﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace PacketRush.Core
{
    /// <summary>
    /// The default server-side networking provider.
    /// </summary>
    /// <remarks>
    /// <c>DefaultServerNetworkingProvider</c> provides server-side networking using TCP for connection and reliable packets and
    /// UDP for unreliable packets.
    /// </remarks>
    public partial class DefaultServerNetworkingProvider : IServerNetworkingProvider
    {
        /// <summary>
        /// Listens for connections
        /// </summary>
        private Socket _ListenSocket;

        private NetConnectionCollection<DefaultConnection> _Connections = new NetConnectionCollection<DefaultConnection>(16, true, true);

        /// <summary>
        /// A temporary storage area for new connections, these will be transfered to the connections
        /// list when fully connected
        /// </summary>
        private Queue<DefaultConnection> _NewConnections = new Queue<DefaultConnection>(16);

        private volatile int _NumConnections = 0;
        /// <summary>
        /// The IASync result associated to the current BeginAccept/EndAccept Async operation
        /// </summary>
        private IAsyncResult _AcceptingConnectionResult;

        /// <summary>
        /// The local end point
        /// </summary>
        private EndPoint _LocalEndPoint;

        /// <summary>
        /// A Udp Socket for unreliable messages
        /// </summary>
        private Socket _UdpSocket;

        /// <summary>
        /// Raised when this provider encounters an error.
        /// </summary>
        public Action<NetErrorEventArgs> ErrorAction { get; set; }

        /// <summary>
        /// Used as a temporary buffer for Unreliable receives
        /// </summary>
        private byte[] _ReceiveBuffer = new byte[Packet.MaxPacketSize];

        /// <summary>
        /// Time measured from when <see cref="PacketRush.Core.DefaultServerNetworkingProvider.Start"/> was called.
        /// </summary>
        /// <remarks>
        /// Time measured from when <see cref="PacketRush.Core.DefaultServerNetworkingProvider.Start"/> was called, resets to 
        /// zero on stop.
        /// </remarks>
        public NetTime RunningTime { get; set; }

        /// <summary>
        /// Settings for the server.
        /// </summary>
        public NetServerSettings ServerSettings { get; set; }

        /// <summary>
        /// Start the networking provider.
        /// </summary>
        /// <param name="localEndPoint">The local end point.</param>
        public void Start(EndPoint localEndPoint)
        {
            _LocalEndPoint = localEndPoint;

            try
            {

                CreateListenSocket();
                CreateUdpSocket();
                TryBeginAcceptConnection();
            }
            catch (SocketException x)
            {
                ErrorAction(new NetErrorEventArgs(null, x.SocketErrorCode));
            }
        }

        #region Connections
        /// <summary>
        /// Accept pending connections.
        /// new connections.
        /// </summary>
        /// <returns>An array of INetConnection.</returns>
        public INetConnection[] AcceptConnections()
        {
            // We do it like this to avoid any potential threading issues with
            // the new connections queue since its used asynchronously
            int count = _NewConnections.Count;
            INetConnection[] connections = new INetConnection[count];
            for (int i = 0; i < count; i++)
            {
                DefaultConnection connection = _NewConnections.Dequeue();
                _Connections.Add(connection);
                connections[i] = connection;
            }

            return connections;
        }

        /// <summary>
        /// Determine if the server is waiting for a connection
        /// </summary>
        /// <returns></returns>
        private bool IsWaitingForConnection()
        {
            if (_AcceptingConnectionResult == null) return false;

            return !_AcceptingConnectionResult.IsCompleted;
        }

        /// <summary>
        /// Try and accept a connecting client
        /// </summary>
        private void TryBeginAcceptConnection()
        {
            if (!IsWaitingForConnection())
            {
                _AcceptingConnectionResult = _ListenSocket.BeginAccept(OnAcceptConnection, null);
            }
        }

        /// <summary>
        /// Occurs when a client connects
        /// </summary>
        /// <param name="result"></param>
        private void OnAcceptConnection(IAsyncResult result)
        {
            try
            {
                Socket clientSocket = _ListenSocket.EndAccept(result);

                DefaultConnection client = new DefaultConnection(clientSocket, ReceivePacketAction);

                client.AssignNewId();

                _NewConnections.Enqueue(client);

                _NumConnections++;
            }
            catch (SocketException x)
            {
                ErrorAction(new NetErrorEventArgs(null, x.SocketErrorCode));
            }

            // Do not allow any more than Max Connections
            if(_NumConnections < ServerSettings.MaxConnections)
                TryBeginAcceptConnection();
        }

        /// <summary>
        /// Disconnect the given connection.
        /// </summary>
        /// <param name="connection">The connection to disconnect.</param>
        public void Disconnect(INetConnection connection)
        {
            DefaultConnection conn = (DefaultConnection)connection;
            _Connections.Remove(conn);
            _NumConnections--;
            try
            {
                conn.Disconnect();
            }
            catch (SocketException x)
            {
                ErrorAction(new NetErrorEventArgs(conn, x.SocketErrorCode));
            }
        }

        #endregion

        /// <summary>
        /// Create the listen socket
        /// </summary>
        private void CreateListenSocket()
        {
            if (_ListenSocket == null)
            {
                _ListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                _ListenSocket.Bind(_LocalEndPoint);
                _ListenSocket.Listen(100);
            }
        }

        /// <summary>
        /// Send a packet to the given connection reliably using the TCP protocol.
        /// </summary>
        /// <param name="target">The target connection to send to.</param>
        /// <param name="packet">The raw packet to send.</param>
        public void SendReliable(INetConnection target, byte[] packet)
        {
            try
            {
                DefaultConnection conn = (DefaultConnection)target;
                conn.Send(packet);
            }
            catch (SocketException x)
            {
                ErrorAction(new NetErrorEventArgs(target, x.SocketErrorCode));
            }  
        }

        /// <summary>
        /// Send a packet to the given connection unreliably using the UDP protocol.
        /// </summary>
        /// <param name="target">The target connection to send to.</param>
        /// <param name="packet">The raw packet to send.</param>
        public void SendUnreliable(INetConnection target, byte[] packet)
        {
            try
            {
                _UdpSocket.SendTo(packet, target.RemoteEndPoint);
            }
            catch (SocketException x)
            {
                ErrorAction(new NetErrorEventArgs(target, x.SocketErrorCode));
            }
        }

        /// <summary>
        /// Receives network packets.
        /// </summary>
        public void Receive()
        {
            ReceiveReliable();
            ReceiveUnreliable();
        }

        /// <summary>
        /// Disconnect this provider from the network.
        /// </summary>
        public void Disconnect()
        {
            _ListenSocket.Close();

            for (int i = 0; i < _Connections.Count; i++)
            {
                DefaultConnection conn = _Connections[i];
                try
                {
                    conn.Disconnect();
                }
                catch (SocketException x)
                {
                    ErrorAction(new NetErrorEventArgs(conn, x.SocketErrorCode));
                }  
            }
            _Connections.Clear();
        }

        private void ReceiveReliable()
        {
            for (int i = 0; i < _Connections.Count; i++)
            {
                try
                {
                    _Connections[i].Receive();
                }
                catch (SocketException x)
                {
                    ErrorAction(new NetErrorEventArgs(_Connections[i], x.SocketErrorCode));
                }  
            }
        }
        private void ReceiveUnreliable()
        {
            EndPoint endPoint = null;
            try
            {
                if (_UdpSocket.Available > 0)
                {
                    endPoint = new IPEndPoint(0, 0);

                    int datagramSize = _UdpSocket.ReceiveFrom(_ReceiveBuffer, ref endPoint);

                    DefaultConnection connection = _Connections.GetByRemoteEndPoint(endPoint);

                    // Do not handle packets from unkown clients
                    if (connection != null)
                    {
                        // Copy this packet into a suitably sized one
                        byte[] rawPacket = new byte[datagramSize];
                        Buffer.BlockCopy(_ReceiveBuffer, 0, rawPacket, 0, datagramSize);

                        ReceivePacketAction(rawPacket, connection);
                    }
                }
            }
            catch (SocketException x)
            {
                ErrorAction(new NetErrorEventArgs(_Connections.GetByRemoteEndPoint(endPoint), x.SocketErrorCode));
            }  
        }

        /// <summary>
        /// Create a UDP socket for unreliable packets
        /// </summary>
        private void CreateUdpSocket()
        {
            if (_UdpSocket == null)
            {
                _UdpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                _UdpSocket.Bind(_LocalEndPoint);
            }
        }

        /// <summary>
        /// Checks for pending outgoing TCP packets.
        /// </summary>
        public void Update()
        {
            // Send pending packets
            for (int i = 0; i < _Connections.Count; i++)
            {
                try
                {
                    _Connections[i].SendPending();
                }
                catch (SocketException x)
                {
                    ErrorAction(new NetErrorEventArgs(_Connections[i], x.SocketErrorCode));
                }  
            }
        }

        /// <summary>
        /// Called when the provider receives packets during a <see cref="PacketRush.Core.DefaultServerNetworkingProvider.Receive"/>.
        /// </summary>
        public Action<byte[], INetConnection> ReceivePacketAction { get; set; }
    }
}